package com.arpa.ntocc.basic.api.controller;


import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.arpa.core.utils.text.EncodeUtil;
import com.arpa.core.web.MediaTypes;
import com.arpa.core.web.RestFulStatus;
import com.arpa.core.web.Result;
import com.arpa.ntocc.common.common.cache.PartyGroupCache;
import com.arpa.ntocc.common.common.constant.StrConsts;
import com.arpa.ntocc.common.common.exception.PermissionException;
import com.arpa.ntocc.common.common.util.LogUtil;
import com.arpa.ntocc.common.common.util.UserUtil;
import com.arpa.ntocc.common.controller.BaseController;
import com.arpa.ntocc.common.domain.dto.UserInfoDTO;
import com.arpa.ntocc.common.domain.entity.*;
import com.arpa.ntocc.common.domain.enums.ClientTypeEnum;
import com.arpa.ntocc.common.domain.enums.PartyStatusEnum;
import com.arpa.ntocc.common.security.annotation.NeedLogin;
import com.arpa.ntocc.common.security.oauth.MyOAuthTokenRequest;
import com.arpa.ntocc.common.service.*;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Maps;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.log4j.Log4j2;
import org.apache.oltu.oauth2.as.issuer.MD5Generator;
import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import static com.arpa.ntocc.common.domain.dto.UserInfoDTO.*;

/**
 * @Description: 登录授权
 * @Author: LEO
 * @Date: 2018/11/3 20:14
 */
@Api(tags = "登录授权")
@RestController
@RequestMapping(value = "", produces = MediaTypes.JSON_UTF_8)
@Log4j2
public class AuthorizeController extends BaseController {

    @Autowired
    private IClientService clientService;
    @Autowired
    private IUserLoginHistoryService userLoginHistoryService;
    @Autowired
    private IUserLoginService userLoginService;
    @Autowired
    private IPartyService partyService;

    @Autowired
    private IPartyGroupService partyGroupService;
    @Autowired
    private PartyGroupCache partyGroupCache;

    private static final String LOGON_AUTHORIZATION_APPLICATION_FAILED = "申请登录授权失败";


    /**
     * @Description: 登录
     * @Author: LEO
     * @Date: 2018/11/3 20:14
     */
    @ApiOperation(value = "获取token", notes = "登录验证获取token")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "client_id", value = "客户端ID", required = true, paramType = "form", dataType = "string", defaultValue = "1e069e240a163e9d2a18f4111ewechat"),
            @ApiImplicitParam(name = "client_secret", value = "客户端安全码", required = true, paramType = "form", dataType = "string", defaultValue = "e7957728041b11ea97dffa163e9d2a30"),
            @ApiImplicitParam(name = "grant_type", value = "grant_type", required = true, paramType = "form", dataType = "string", defaultValue = "password"),
            @ApiImplicitParam(name = "response_type", value = "response_type", required = true, paramType = "form", dataType = "string", defaultValue = "token"),
            @ApiImplicitParam(name = "device_id", value = "设备id", paramType = "form", dataType = "string", defaultValue = "0000000000"),
            @ApiImplicitParam(name = "username", value = "用户名", required = true, paramType = "form", dataType = "string", defaultValue = ""),
            @ApiImplicitParam(name = "password", value = "密码", required = true, paramType = "form", dataType = "string", defaultValue = ""),
            @ApiImplicitParam(name = "time", value = "时间校验因子", required = true, paramType = "form", dataType = "string", defaultValue = ""),
            @ApiImplicitParam(name = "login_type", value = "登录类型:SHIPMENT", paramType = "form", dataType = "string")
    })
    @PostMapping(value = "/authorize")
    public Result authorize(
            HttpServletRequest request) {
        String username = "";

        try {
            MyOAuthTokenRequest oauthRequest = new MyOAuthTokenRequest(request);
            username = oauthRequest.getUsername();
            String clientId = oauthRequest.getClientId();
            String clientSecret = oauthRequest.getClientSecret();
            String password = oauthRequest.getPassword();
            String time = request.getParameter("time");
            //校验客户端应用ID和安全码
            Client client = clientService.getClient(clientId, clientSecret);
            if (client == null || StrUtil.isBlank(client.getType())) {
                log.error(LogUtil.log("客户端应用ID【" + clientId + "】和安全码【" + clientSecret + "】验证失败！"));
                return Result.error(RestFulStatus.OAUTH2_INVALID_CLIENT);
            }
            //
            String clientType = client.getType();
            //校验密码和时间因子
            boolean validate = authorizeTokenService.validatePassAndTime(password, time);
            if (!validate) {
                log.error(LogUtil.log("密码或时间因子验证失败！"));
                return Result.error("密码或时间因子验证失败!");
            }

            password = authorizeTokenService.decodePassword(password);
            UserLoginHistory userLoginHistory = new UserLoginHistory();
            userLoginHistory.setFromDate(LocalDateTime.now());
            Subject subject = SecurityUtils.getSubject();
            try {
                //登录账号信息
                UserLogin userLogin = userLoginService.getUserLoginByUserLoginId(username);
                if (userLogin == null) {
                    return Result.error(RestFulStatus.OAUTH2_AUTHORIZE_INVALID);
                }
                String systemType = getSystemType(clientType);

                UsernamePasswordToken token = new UsernamePasswordToken(username, password);
                subject.login(token);
                //获取用户信息
                Party party = partyService.getByCode(userLogin.getPartyCode());
                if (party == null || PartyStatusEnum.DISABLED.equals(party.getStatus()) ) {
                    subject.logout();
                    log.error(LogUtil.log("账号不正确或已禁用！"));
                    return Result.error("账号不正确或已禁用!");
                }
                PartyGroup partyGroup = partyGroupService.getOne(Wrappers.lambdaQuery(PartyGroup.class)
                        .select(PartyGroup::getStatus)
                        .eq(PartyGroup::getCode,party.getBranchCode()));
                if(partyGroup==null||partyGroup.getStatus()==0){
                    subject.logout();
                    log.error(LogUtil.log("账号组织不存在或未启用！"));
                    return Result.error("账号组织不存在或未启用!");
                }


                //登录用户的授权数据
                Map<String, Object> authorizeData = new HashMap<>();
                ////登录时选择的仓库或货主的标识
                //authorizeData.put(AUTHORIZE_DATA_CODE_KEY, authorizeDataCode);
                String authorizationShipment = party.getAuthorizationShipment();
                if (StrUtil.isNotBlank(authorizationShipment)) {
                    String[] authorizationShipmentCode = StrUtil.split(authorizationShipment, ",");
                    if (ArrayUtil.isNotEmpty(authorizationShipmentCode)) {
                        String authorizationShipmentCodeStr =  Arrays.stream(authorizationShipmentCode)
                                .map(s -> StrUtil.unWrap(s, "[", "]")).collect(Collectors.joining(","));
                        //登录用户的货主权限
                        authorizeData.put(AUTHORIZE_SHIPMENT_CODES, authorizationShipmentCodeStr);
                    }
                }
                String authorizationWarehouse = party.getAuthorizationWarehouse();
                if (StrUtil.isNotBlank(authorizationWarehouse)) {
                    String[] authorizationWarehouseCode = StrUtil.split(authorizationWarehouse, ",");
                    if (ArrayUtil.isNotEmpty(authorizationWarehouseCode)) {
                       String authorizationWarehouseCodeStr =  Arrays.stream(authorizationWarehouseCode)
                                .map(s -> StrUtil.unWrap(s, "[", "]")).collect(Collectors.joining(","));
                        //登录用户的仓库权限
                        authorizeData.put(AUTHORIZE_WAREHOUSE_CODES, authorizationWarehouseCodeStr);
                    }
                }

                //登录系统类型
                authorizeData.put(AUTHORIZE_DATA_TYPE_KEY, systemType);
                //生成Access Token
                OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
                final String accessToken = oauthIssuerImpl.accessToken();
                authorizeTokenService.addAuthorizeToken(client.getType(), accessToken, party.getCode());
                authorizeTokenService.getUserInfo(party, authorizeData,client.getType());
                userLoginHistory.setUserLoginId(username);
                userLoginHistory.setPartyCode(party.getCode());
                userLoginHistory.setSuccessfulLogin("Y");
                userLoginHistoryService.save(userLoginHistory);
                Map<String, Object> resultMap = Maps.newHashMap();
                resultMap.put("access_token", accessToken);
                resultMap.put("party", party);
                resultMap.put("authorizeData", authorizeData);
                //添加机构信息
                resultMap.put("branch", partyGroupCache.getSimple(party.getBranchCode()));
                return Result.ok().data(resultMap);
            } catch (org.apache.shiro.authc.UnknownAccountException ex) {
                //账号不存在
                log.error(LogUtil.log(LOGON_AUTHORIZATION_APPLICATION_FAILED + ex.getMessage()));
                return Result.error(RestFulStatus.OAUTH2_AUTHORIZE_INVALID);
            } catch (org.apache.shiro.authc.IncorrectCredentialsException ex) {
                //保存密码错误登录失败记录
                userLoginHistory.setUserLoginId(username);
                userLoginHistory.setPartyCode("");
                userLoginHistory.setSuccessfulLogin("N");
                userLoginHistory.setPasswordUsed(EncodeUtil.encodeMD5Hex(password));
                userLoginHistoryService.save(userLoginHistory);
                log.error(LogUtil.log(LOGON_AUTHORIZATION_APPLICATION_FAILED + ex.getMessage()));
                return Result.error(RestFulStatus.OAUTH2_AUTHORIZE_INVALID);
            } catch (org.apache.shiro.authc.DisabledAccountException ex) {
                log.error(LogUtil.log(LOGON_AUTHORIZATION_APPLICATION_FAILED + ex.getMessage()));
                //帐号已禁用
                return Result.error(RestFulStatus.OAUTH2_ACCOUNT_DISABLED);
            } catch (ExcessiveAttemptsException ex) {
                log.error(LogUtil.log("申请登录授权失败，登录失败次数过多" + ex.getMessage()));
                return Result.error("登录失败次数过多，请过5分钟再试！");
            }
        } catch (OAuthProblemException e) {
            log.error(LogUtil.log(LOGON_AUTHORIZATION_APPLICATION_FAILED + e.getMessage()));
            return Result.error(RestFulStatus.OAUTH2_TEMPORARILY_UNAVAILABLE);
        } catch (PermissionException ex) {
            log.error(LogUtil.log(LOGON_AUTHORIZATION_APPLICATION_FAILED + ex.getMessage()));
            return Result.error(RestFulStatus.OAUTH2_ACCOUNT_UNAUTHORIZED);
        } catch (Exception ex) {
            log.error(LogUtil.log(LOGON_AUTHORIZATION_APPLICATION_FAILED + ex.getMessage()));
            return Result.error(RestFulStatus.INTERNAL_SERVER_ERROR);
        }
    }


    /**
     * 判断当前用户是否有登录当前系统的权限
     *
     * @param userLoginType 用户授权登录系统  WAREHOUSE(WMS)、SHIPMENT(OMS)
     * @param clientType    当前要登录的系统
     */
    private boolean isAuthorizedSystem(String userLoginType, String clientType) {
        ClientTypeEnum loginSystem = ClientTypeEnum.valueOf(clientType);
        switch (loginSystem) {
            case WEB_OMS: {
                if (StrUtil.contains(userLoginType, UserLogin.USER_LOGIN_TYPE_SHIPMENT)) {
                    return true;
                }
                break;
            }
            case WEB_WMS: {
                if (StrUtil.contains(userLoginType, UserLogin.USER_LOGIN_TYPE_WAREHOUSE)) {
                    return true;
                }
                break;
            }
            case WMS_PDA: {
                return true;
            }
            default:
        }
        return false;
    }

    /**
     * 获取clientType对应的系统
     *
     * @param clientType 客户端类型
     * @return
     */
    private String getSystemType(String clientType) {
        ClientTypeEnum loginSystem = ClientTypeEnum.valueOf(clientType);
        switch (loginSystem) {
            case WEB_OMS: {
                return UserLogin.USER_LOGIN_TYPE_SHIPMENT;
            }
            case WEB_WMS: {
                return UserLogin.USER_LOGIN_TYPE_WAREHOUSE;
            }
            default:
        }
        return "";

    }

    /**
     * 查询登录账号的用户信息，角色信息，权限信息
     *
     * @param request
     * @return
     */
    @ApiOperation(value = "查询登录账号的用户信息", notes = "查询登录账号的用户信息，角色信息，权限信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "Authorization", value = "授权token", required = true, paramType = "header", dataType = "String", defaultValue = "Bearer 123456"),
            @ApiImplicitParam(name = "deviceId", value = "设备id", required = true, paramType = "header", dataType = "string", defaultValue = "0000000000")
    })
    @NeedLogin
    @PostMapping(value = "/getUserInfo")
    public Result getUserInfo(HttpServletRequest request) {
        try {
            String partyCode = getCurrentPartyCode();
            UserInfoDTO userInfoDTO = authorizeTokenService.getUserInfo(partyCode);
            if (userInfoDTO == null) {
                return Result.error("没有查询到登录账号信息，请重新登录");
            } else {
                userInfoDTO.setBranch(UserUtil.getBranch());
                return Result.ok().data(userInfoDTO);
            }
        } catch (Exception e) {
            log.error(StrConsts.CATCH_MESSAGE, e);
            log.error(LogUtil.log("查询登录账号的用户信息失败" + e.getMessage()));
            return Result.error(e.getMessage());
        }
    }


    @ApiOperation(value = "切换登录信息", notes = "切换登录信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "Authorization", value = "授权token", required = true, paramType = "header", dataType = "String", defaultValue = "Bearer 123456"),
            @ApiImplicitParam(name = "dataType", value = "系统类型", required = true, paramType = "query", dataType = "string"),
            @ApiImplicitParam(name = "authorizeDataCode", value = "切换后标识 仓库标识/货主标识", required = true, paramType = "query", dataType = "string")
    })
    @NeedLogin
    @PostMapping(value = "/changeLogin")
    public Result changeLogin(HttpServletRequest request,String dataType,String authorizeDataCode) {
        try {
            String partyCode = getCurrentPartyCode();
            Map<String, Object> authorizeDate = authorizeTokenService.getAuthorizeData(partyCode);
            authorizeDate.put(AUTHORIZE_DATA_CODE_KEY,authorizeDataCode);
            if (StrUtil.equals(UserLogin.USER_LOGIN_TYPE_WAREHOUSE, dataType)) {
                authorizeDate.put(AUTHORIZE_DATA_KEY, userLoginService.getLoginChooseWarehouseInfo(authorizeDataCode));
            }
            if (StrUtil.equals(UserLogin.USER_LOGIN_TYPE_SHIPMENT, dataType)) {
                authorizeDate.put(AUTHORIZE_DATA_KEY, userLoginService.getLoginChooseShipmentInfo(authorizeDataCode));


            }
            UserInfoDTO userInfoDTO = authorizeTokenService.changeUserInfo(partyCode,authorizeDate,dataType,authorizeDataCode);
            return Result.ok().data(userInfoDTO);
        } catch (Exception e) {
            log.error(StrConsts.CATCH_MESSAGE, e);
            log.error(LogUtil.log("查询登录账号的用户信息失败" + e.getMessage()));
            return Result.error(e.getMessage());
        }
    }

    /**
     * @param request
     * @Description: 退出登录
     * @Author: LEO
     * @Date: 2018/10/30 08:52
     */
    @ApiOperation(value = "退出登录", notes = "退出登录")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "Authorization", value = "授权token", required = true, paramType = "header", dataType = "String", defaultValue = "Bearer 123456"),
            @ApiImplicitParam(name = "deviceId", value = "设备id", required = true, paramType = "header", dataType = "string", defaultValue = "0000000000")
    })
    @GetMapping(value = "/logout")
    public Result logout(HttpServletRequest request) {
        try {
            //退出逻辑已在过滤器OauthLogoutFilter处理
            log.info(LogUtil.log("退出成功"));
            return Result.ok("操作成功！");
        } catch (Exception e) {
            log.error(LogUtil.log("退出登录失败" + e.getMessage()));
            return Result.error(e.getMessage());
        }
    }

}
